namespace CacheProvider
{
    #region N A M E S P A C E   I M P O R T S

    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Configuration;
    using System.Data;
    using System.Data.SqlClient;
    using System.Diagnostics;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using CacheProviderEntities;
    using CacheProviderInterfaces;
    using Microsoft.Practices.Composite.Presentation.Events;

    #endregion

    public class SqlMonitoringManager : IDependencyMonitoringManager
    {
        private class MonitoringInfo
        {
            public DateTime MonitoringStartTime { get; set; }
            public CacheDependencyStatus Status { get; set; }
        }

        #region P R I V A T E   A T T R I B U T E S

        private readonly CacheProvider _cacheProvider;

        private readonly ILogWriter _logWriter;

        //Dictionary used to store key=connectionstring, value=MonitoringInfo
        //to track the connections which are being monitored.
        private readonly HybridDictionary _monitoringDictionary;

        //used to track cache dependencies
        private readonly List<CacheDependencyTracker> _cacheDependencyTracker;

        //Serializes access to the _cacheDependencyTracker list.  Facilitates multiple readers to simultaneously
        //access the list while guaranteeing exclusive access for only one writer at a time.
        private static readonly ReaderWriterLockSlim DependencyTrackerLocker = new ReaderWriterLockSlim();

        //serializes access to _monitoringDictionary
        private static readonly object DictionarySerializer = new object();

        //private readonly IDictionary<string, ManualResetEvent> _keysUnderDataChangeNotificationProcessing
        //                                            = new Dictionary<string, ManualResetEvent>();

        ////serializes access to _keysUnderDataChangeNotificationProcessing Dictionary
        //private static readonly object NotificationKeyListSerializer = new object();

        private int _executeRetryCount = 5;
        #endregion

        #region C O N S T R U C T O R S

        public SqlMonitoringManager(IDependencyInjectionContainer dependencyInjectionContainer)
        {
            this._cacheProvider = dependencyInjectionContainer.Resolve<CacheProvider>();
            this._logWriter = dependencyInjectionContainer.Resolve<ILogWriter>();
            this._monitoringDictionary = new HybridDictionary();
            this._cacheDependencyTracker = new List<CacheDependencyTracker>();
        }

        #endregion

        #region P U B L I C   M E T H O D S

        /// <summary>
        /// Sets up monitoring for SqlDependency Management.
        /// </summary>
        /// <param name="dbConnectionString">SQL Server DB connection String for which to monitor dependencies</param>
        /// <returns>Returns <see cref="CacheDependencyStatus"/></returns>
        public CacheDependencyStatus StartMonitoring(string dbConnectionString)
        {
            var monitoringStatus = CacheDependencyStatus.Unknown;

            if (string.IsNullOrEmpty(dbConnectionString))
            {
                monitoringStatus = CacheDependencyStatus.InvalidConnectionString;
            }
            else
            {
                lock (DictionarySerializer)
                {
                    if (_monitoringDictionary.Contains(dbConnectionString))
                    {
                        var monitoringInfo = _monitoringDictionary[dbConnectionString] as MonitoringInfo;

                        if (monitoringInfo != null)
                            if (monitoringInfo.Status == CacheDependencyStatus.MonitoringStarted)
                            {
                                monitoringStatus = CacheDependencyStatus.AlreadyMonitoring;
                                return monitoringStatus;
                            }
                    }

                    var isCurrentlyMonitoring = SqlDependency.Start(dbConnectionString);

                    if (isCurrentlyMonitoring)
                    {
                        var monitoringInfo = new MonitoringInfo()
                        {
                            Status = CacheDependencyStatus.MonitoringStarted,
                            MonitoringStartTime = DateTime.UtcNow

                        };
                        this._monitoringDictionary.Add(dbConnectionString, monitoringInfo);
                        monitoringStatus = CacheDependencyStatus.MonitoringStarted;
                    }
                }
            }

            return monitoringStatus;
        }

        /// <summary>
        /// Sets up monitoring for SqlDependency Changes.
        /// </summary>
        /// <param name="dbConnectionString">SQL Server DB connection String for which to stop monitoring dependencies</param>
        /// <returns>Returns <see cref="CacheDependencyStatus"/></returns>
        public CacheDependencyStatus StopMonitoring(string dbConnectionString)
        {
            var monitoringStatus = CacheDependencyStatus.Unknown;

            if (string.IsNullOrEmpty(dbConnectionString))
            {
                monitoringStatus = CacheDependencyStatus.InvalidConnectionString;
            }
            else
            {
                lock (DictionarySerializer)
                {
                    if (_monitoringDictionary.Contains(dbConnectionString))
                    {
                        var monitoringInfo = _monitoringDictionary[dbConnectionString] as MonitoringInfo;
                        if (monitoringInfo != null)
                        {
                            var isMonitoringStopped = SqlDependency.Stop(dbConnectionString);
                            if (isMonitoringStopped)
                            {
                                _monitoringDictionary.Remove(dbConnectionString);
                                monitoringStatus = CacheDependencyStatus.MonitoringStopped;
                            }
                        }
                    }
                    else
                    {
                        monitoringStatus = CacheDependencyStatus.NotRegistered;
                    }
                }
            }

            return monitoringStatus;
        }

        /// <summary>
        /// Registers SqlDependencies for monitoring changes. Has provision for
        /// automatic cache updation (when dependencies change) through callback registration.
        /// </summary>
        /// <typeparam name="T">Type of Payload with which the callback should be invoked.</typeparam>
        /// <param name="key">Cache key</param>
        /// <param name="namedCache">Name of the Named cache (if any & supported)</param>
        /// <param name="regionName">If Region is supported, Name of the Region where the key\value will remain</param>
        /// <param name="dependencyInfo">An instance of <see cref="DependencyInfo" with <see cref="DependencyInfo.DependencyTypes.SQLDependency"/>/> and other suitable properties set.</param>
        /// <param name="encacheDependencies">Whether to store the dependencies in the Cache.  Useful for Distributed cache providers to provide dependency management fallback.</param>
        /// <param name="cacheUpdationCallback">The callback delegate to invoke in case dependencies change.</param>
        /// <param name="callbackState">Payload instance with which the callback should be invoked.</param>
        /// <param name="threadOption">Thread on which the callback should be invoked. See <see cref="ThreadOption"/> for details</param>
        /// <param name="keepSubscriberReferenceAlive">Should there be a strong or weak reference to the callback.</param>
        /// <param name="delayForProcessingDependencyChange">Timespan to signify delay to be introduced while processing a Dependency Change notification.  If it used
        /// so that the win Service does not process before the client(if it is alive) because the client can invoke automatic update callbacks.</param>
        public void AddSqlDependencies<T>(string key,
                                        string namedCache,
                                        string regionName,
                                        DependencyInfo dependencyInfo,
                                        bool encacheDependencies,
                                        Action<T> cacheUpdationCallback,
                                        T callbackState,
                                        ThreadOption threadOption,
                                        bool keepSubscriberReferenceAlive,
                                        TimeSpan delayForProcessingDependencyChange,
                                        Object cacheItemVersionData
            )
        {

            if (dependencyInfo == null)
                return;
            if (dependencyInfo.CacheDependencyType != CacheDependencyTypes.SQLDependency)
                throw new ArgumentException("dependencyInfo.CacheDependencyType");
            if (string.IsNullOrEmpty(dependencyInfo.DBConnectionString))
            {
                var cacheProviderErrorEventArgs = new CacheProviderErrorEventArgs()
                {
                    Sender = this,
                    CacheKey = key,
                    ErrorMessage = "DependencyInfo.DBConnectionString property is null"
                };


                this._cacheProvider.EventAggregatorInstance.GetEvent<CacheProviderErrorEvent>().Publish(cacheProviderErrorEventArgs);

                return;
            }

            CacheDependencyTracker cacheDependencyTracker = null;
            var sqlCommands = new List<SqlCommand>();


            #region SQL QUERIES PROCESSING

            if (!string.IsNullOrEmpty(dependencyInfo.DBConnectionString)
                && dependencyInfo.IsSQLSelectQueryBased
                && dependencyInfo.SelectQueries != null && dependencyInfo.SelectQueries.Count > 0
                )
            {
                foreach (var sqlQuery in dependencyInfo.SelectQueries)
                {
                    if (cacheDependencyTracker == null)
                    {
                        cacheDependencyTracker = new CacheDependencyTracker()
                        {
                            Key = key,
                            DependencyData = dependencyInfo,
                            NamedCache = namedCache,
                            RegionName = regionName,
                            AreDependenciesCached = encacheDependencies,
                            DelayInDependencyNotificationProcessing = delayForProcessingDependencyChange
                        };
                    }

                    var sqlCommand = new SqlCommand(sqlQuery);
                    sqlCommands.Add(sqlCommand);
                    var sqlDependency = new SqlDependency(sqlCommand);
                    sqlDependency.OnChange += SqlDependency_OnChange;

                    cacheDependencyTracker.SqlDependencyInstances.Add(sqlDependency);
                }
            }

            #endregion

            #region SQL TABLES PROCESSING

            if (!string.IsNullOrEmpty(dependencyInfo.DBConnectionString)
                && dependencyInfo.IsSQLTableBased
                && dependencyInfo.SQLTables != null && dependencyInfo.SQLTables.Count > 0)
            {
                using (var connection = new SqlConnection(dependencyInfo.DBConnectionString))
                {
                    foreach (var table in dependencyInfo.SQLTables)
                    {
                        if (cacheDependencyTracker == null)
                        {
                            cacheDependencyTracker = new CacheDependencyTracker()
                            {
                                Key = key,
                                DependencyData = dependencyInfo,
                                NamedCache = namedCache,
                                RegionName = regionName,
                                AreDependenciesCached = encacheDependencies,
                                DelayInDependencyNotificationProcessing = delayForProcessingDependencyChange
                            };
                        }

                        var sqlQuery = GetSQLQuery(connection, table);

                        var sqlCommand = new SqlCommand(sqlQuery);
                        sqlCommands.Add(sqlCommand);
                        var sqlDependency = new SqlDependency(sqlCommand);
                        sqlDependency.OnChange += SqlDependency_OnChange;

                        cacheDependencyTracker.SqlDependencyInstances.Add(sqlDependency);
                    }
                }
            }

            #endregion

            #region SQL COMMANDS PROCESSING

            if (!string.IsNullOrEmpty(dependencyInfo.DBConnectionString)
               && dependencyInfo.ISDbCommandBased
               && dependencyInfo.DbCommands != null && dependencyInfo.DbCommands.Count > 0)
            {

                using (var connection = new SqlConnection(dependencyInfo.DBConnectionString))
                {
                    foreach (var dbCommand in dependencyInfo.DbCommands)
                    {
                        if (cacheDependencyTracker == null)
                        {
                            cacheDependencyTracker = new CacheDependencyTracker()
                            {
                                Key = key,
                                DependencyData = dependencyInfo,
                                NamedCache = namedCache,
                                RegionName = regionName,
                                AreDependenciesCached = encacheDependencies,
                                DelayInDependencyNotificationProcessing = delayForProcessingDependencyChange
                            };
                        }
                        var sqlCommand = dbCommand as SqlCommand;

                        Debug.Assert(sqlCommand != null, "DBCommand must be a SqlCommand here!");

                        dbCommand.Connection = connection;

                        sqlCommands.Add(sqlCommand);
                        var sqlDependency = new SqlDependency(sqlCommand);
                        sqlDependency.OnChange += SqlDependency_OnChange;

                        cacheDependencyTracker.SqlDependencyInstances.Add(sqlDependency);
                    }
                }
            }

            #endregion


            if (cacheDependencyTracker == null) return;

            bool wasDepTrackerAddedSuccessfully;

            try
            {
                DependencyTrackerLocker.EnterWriteLock();

                //This is required as the Cache Server does not remove an
                //item from Cache as soon as it expires.   Due to that, whenever
                //it removes the item, then, only it raises the notification.  Since,
                // we depend on notification from the Cache Server, the user may try 
                // to add the same key again in between with same\different value.
                this.RemoveItemFromDependencyTracker(key, namedCache, regionName, null);

                this._cacheDependencyTracker.Add(cacheDependencyTracker);

                if (cacheItemVersionData != null)
                    cacheDependencyTracker.CacheItemDataVersion = cacheItemVersionData;

                if (encacheDependencies)
                    ThreadPool.QueueUserWorkItem(EncacheDependencies);

                wasDepTrackerAddedSuccessfully = true;
            }
            finally
            {
                DependencyTrackerLocker.ExitWriteLock();
            }

            #region SQL DEPENDENCY COMMMANDS EXECUTION

            //commands are executed only after the Dependency Tracker list is updated to avoid a race
            //condition where dependency change notification may be triggered even before the Dependency Tracker is updated.
            if (wasDepTrackerAddedSuccessfully && sqlCommands.Count() > 0 && dependencyInfo.DBConnectionString != null)
            {
                using (var connection = new SqlConnection(dependencyInfo.DBConnectionString))
                {
                    connection.Open();
                    foreach (var sqlCommand in sqlCommands)
                    {
                        sqlCommand.Connection = connection;
                        bool noOfRetryExceeds;
                        var noOfRetry = 0;
                        //Do-While loop till query registration succeeded.
                        do
                        {
                            try
                            {
                                sqlCommand.ExecuteNonQuery();
                                noOfRetryExceeds = true;
                            }
                            catch (SqlException exception)
                            {
                                //Deadlock exception number: 1205.we should retry the registration
                                if (exception.Number != 1205)
                                    throw;
                                if (noOfRetry == 0)
                                {
                                    var message = string.Format("{0} - query registration enters into transaction deadlock.",
                                                            sqlCommand.CommandText);
                                    this._logWriter.Write(message, this._cacheProvider.LoggingCategory, 1,
                                                          (int)LoggingEvent.CacheNotification, TraceEventType.Warning, key);
                                }
                                //Following code will retry the query registration with delay of 
                                //(2^n * 1000) seconds where n is current count of retry. It will retry for maximum
                                //of five time. This time delay is required to make previous transaction to be completed.
                                //Otherwise in subsequent attempts, registration will end up in deadlock situation.
                                var delayTime = Math.Pow(2, noOfRetry) * 1000;
                                Thread.Sleep((int)delayTime);
                                noOfRetry++;
                                noOfRetryExceeds = (noOfRetry == this._executeRetryCount);
                                //If registration fails after 5 attempts, SqlMontoringManager will
                                //log the error message and continue with other dependency registration.
                                if (noOfRetryExceeds)
                                {
                                    var message = string.Format("{0} - Query fails to register the dependency for key: {1}", sqlCommand.CommandText, key);
                                    this._logWriter.Write(message, this._cacheProvider.LoggingCategory, 1,
                                                          (int)LoggingEvent.CacheNotification, TraceEventType.Critical, key);
                                }
                            }
                        }
                        while (!noOfRetryExceeds);
                    }
                }
            }

            #endregion

            //Register callback for automatic cache updatation.
            if (wasDepTrackerAddedSuccessfully && cacheUpdationCallback != null)
                cacheDependencyTracker.CallbackManager.RegisterCallback(cacheUpdationCallback,
                                                                        callbackState,
                                                                        threadOption,
                                                                        keepSubscriberReferenceAlive);
        }



        /// <summary>
        /// Unregisters an already registered SqlDependency from monitoring changes.
        /// </summary>
        /// <param name="key">Cache Key with which Dependency was registered.</param>
        /// <param name="cacheName">Named Cache with which Dependency was registered, else null</param>
        /// <param name="regionName">Name of the region with which dependency was registered, else null</param>
        /// <param name="serializableCacheItemVersion"></param>
        /// <returns>True if the dependency exists and is unregistered else false</returns>
        public bool RemoveSqlDependency(string key, string cacheName, string regionName, object serializableCacheItemVersion)
        {
            //Cache Entry Removal Callback processing for Classic Cache Provider happens on the same thread.  
            //In that case, the method which invokes the Cache Entry Removal and the callback from Cache store
            //happens on the same thread and are trying to hold the ReaderWriterLockSlim recursively.  
            //Hence, checking the same before acquiring the lock.

            var isWriteLockAlreadyHeldByCurrentThread = false;
            try
            {
                isWriteLockAlreadyHeldByCurrentThread = DependencyTrackerLocker.IsWriteLockHeld;
                if (!isWriteLockAlreadyHeldByCurrentThread)
                    DependencyTrackerLocker.EnterWriteLock();
                return RemoveItemFromDependencyTracker(key, cacheName, regionName, serializableCacheItemVersion);
            }
            finally
            {
                if (!isWriteLockAlreadyHeldByCurrentThread)
                    DependencyTrackerLocker.ExitWriteLock();
            }
        }


        public void UpdateDataChangeProcessingDelay(string key, string cacheName, string regionName, TimeSpan newDelay)
        {
            try
            {
                DependencyTrackerLocker.EnterWriteLock();

                var dependencyTracker = this.GetDependencyTracker(regionName, cacheName, key);

                if (dependencyTracker != null)
                {
                    dependencyTracker.DelayInDependencyNotificationProcessing = newDelay;
                }
            }
            finally
            {
                DependencyTrackerLocker.ExitWriteLock();
            }
        }


        public void RemoveAllDependencyTrackers()
        {
            var isWriteLockAlreadyHeldByCurrentThread = false;
            //Find the dependency from the list
            try
            {
                isWriteLockAlreadyHeldByCurrentThread = DependencyTrackerLocker.IsWriteLockHeld;
                if (!isWriteLockAlreadyHeldByCurrentThread)
                    DependencyTrackerLocker.EnterWriteLock();
                if (this._cacheDependencyTracker == null)
                    return;
                foreach (var cacheDependencyTracker in _cacheDependencyTracker)
                {
                    if (cacheDependencyTracker != null)
                    {

                        //unsubscribe the dependency notification.
                        if (cacheDependencyTracker.DependencyData.CacheDependencyType == CacheDependencyTypes.SQLDependency)
                        {
                            foreach (var sqlDependency in cacheDependencyTracker.SqlDependencyInstances)
                            {
                                sqlDependency.OnChange -= SqlDependency_OnChange;
                            }

                        }
                    }
                }

                this._cacheDependencyTracker.Clear();
            }
            finally
            {
                if (!isWriteLockAlreadyHeldByCurrentThread)
                    DependencyTrackerLocker.ExitWriteLock();
            }
        }


        #endregion

        #region P R I V A T E   M E T H O D S

        private bool RemoveItemFromDependencyTracker(string key, string cacheName, string regionName, object serializableCacheItemVersion)
        {
            var isRemoved = false;

            //Find the dependency from the list
            var dependencyTracker = this.GetDependencyTracker(regionName, cacheName, key);

            if (dependencyTracker != null)
            {
                var areCacheVersionsSame = true;

                if (serializableCacheItemVersion != null)
                    areCacheVersionsSame = this._cacheProvider.IsCacheVersionSame(serializableCacheItemVersion,
                                                                                  dependencyTracker.CacheItemDataVersion);

                if (areCacheVersionsSame)
                {
                    //unsubscribe the dependency notification.
                    if (dependencyTracker.DependencyData.CacheDependencyType == CacheDependencyTypes.SQLDependency)
                    {
                        //dependencyTracker.SqlDependencyInstance.OnChange -= SqlDependency_OnChange;
                        foreach (var sqlDependency in dependencyTracker.SqlDependencyInstances)
                        {
                            sqlDependency.OnChange -= SqlDependency_OnChange;
                        }

                    }
                    //Remove it from the list.
                    isRemoved = this._cacheDependencyTracker.Remove(dependencyTracker);

                    //If dependencies are to be cached, schedule caching the same on a separate threadpool thread.
                    if (dependencyTracker.AreDependenciesCached)
                        ThreadPool.QueueUserWorkItem(EncacheDependencies);

                }
                else
                {
                    var message = string.Format("ApplicationName {0}--> Cache Item versions are not same for key {1}, Region {2} in Named Cache {3}.  Hence, not removing it from tracker!",
                            ConfigurationManager.AppSettings["ApplicationName"]
                            , key
                            , regionName
                            , cacheName);
                    if (this._logWriter != null)
                        this._logWriter.Write(message, this._cacheProvider.LoggingCategory, 1, (int)LoggingEvent.CacheNotification, TraceEventType.Critical, key);

                }
            }

            return isRemoved;
        }

        private CacheDependencyTracker GetDependencyTracker(string regionName, string cacheName, string key)
        {
            //try
            //{
            //    DependencyTrackerLocker.EnterReadLock();

            CacheDependencyTracker dependencyTracker;

            if (this._cacheProvider.IsRegionSupported && !string.IsNullOrEmpty(regionName))
            {
                if (this._cacheProvider.IsNamedCacheSupported && !string.IsNullOrEmpty(cacheName))
                {
                    dependencyTracker = (from depInfo in this._cacheDependencyTracker
                                         where depInfo.Key.Equals(key)
                                               && depInfo.RegionName.Equals(regionName)
                                               && depInfo.NamedCache.Equals(cacheName)
                                         select depInfo).FirstOrDefault();
                }
                else
                {
                    dependencyTracker = (from depInfo in this._cacheDependencyTracker
                                         where depInfo.Key.Equals(key)
                                               && depInfo.RegionName.Equals(regionName)
                                         select depInfo).FirstOrDefault();
                }
            }
            else
            {
                if (this._cacheProvider.IsNamedCacheSupported && !string.IsNullOrEmpty(cacheName))
                {
                    dependencyTracker = (from depInfo in this._cacheDependencyTracker
                                         where depInfo.Key.Equals(key)
                                               && depInfo.NamedCache.Equals(cacheName)
                                         select depInfo).FirstOrDefault();
                }
                else
                {
                    dependencyTracker = (from depInfo in this._cacheDependencyTracker
                                         where depInfo.Key.Equals(key)
                                         select depInfo).FirstOrDefault();
                }
            }
            return dependencyTracker;
            //}
            //finally
            //{
            //    DependencyTrackerLocker.ExitReadLock();
            //}
        }

        /// <summary>
        /// Callback for SqlDependency Change.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SqlDependency_OnChange(object sender, SqlNotificationEventArgs e)
        {
            //var keyBeingProcessed = string.Empty;
            //try
            //{
            //CacheDependencyTracker cacheDependencyTracker = null;
            //try
            //{
            //    DependencyTrackerLocker.EnterReadLock();

            //    if (sender is SqlDependency)
            //    {
            //        cacheDependencyTracker = (from dependencyInfo in this._cacheDependencyTracker
            //                                  let sqlInstanceIds =
            //                                      dependencyInfo.SqlDependencyInstances.Select(x => x.Id)
            //                                  where sqlInstanceIds.Contains((sender as SqlDependency).Id)
            //                                  select dependencyInfo).FirstOrDefault();
            //    }

            //    if (cacheDependencyTracker != null)
            //    {
            //        keyBeingProcessed = string.Join("#", new[]
            //                                                 {
            //                                                     cacheDependencyTracker.Key,
            //                                                     cacheDependencyTracker.RegionName,
            //                                                     cacheDependencyTracker.NamedCache
            //                                                 });
            //        lock (NotificationKeyListSerializer)
            //        {
            //            if (!this._keysUnderDataChangeNotificationProcessing.ContainsKey(keyBeingProcessed))
            //                this._keysUnderDataChangeNotificationProcessing.Add(keyBeingProcessed, new ManualResetEvent(false));
            //        }
            //    }

            //}
            //finally
            //{
            //    DependencyTrackerLocker.ExitReadLock();
            //}

            //http://msdn.microsoft.com/en-us/library/ms189308.aspx
            SqlNotificationState sqlNotificationInfo = null;

            switch (e.Info)
            {
                case SqlNotificationInfo.Error:
                case SqlNotificationInfo.Expired:
                case SqlNotificationInfo.Invalid:
                case SqlNotificationInfo.Resource:
                case SqlNotificationInfo.Restart:
                case SqlNotificationInfo.TemplateLimit:
                case SqlNotificationInfo.Unknown:

                    CacheDependencyTracker dependencyData = null;
                    try
                    {
                        DependencyTrackerLocker.EnterWriteLock();

                        if (sender is SqlDependency)
                        {
                            dependencyData = (from dependencyInfo in this._cacheDependencyTracker
                                              let sqlInstanceIds =
                                                  dependencyInfo.SqlDependencyInstances.Select(x => x.Id)
                                              where sqlInstanceIds.Contains((sender as SqlDependency).Id)
                                              select dependencyInfo).FirstOrDefault();
                        }

                        if (dependencyData != null && this._cacheProvider != null)
                        {
                            this._cacheProvider.Remove(dependencyData.Key);
                            this.RemoveItemFromDependencyTracker(dependencyData.Key, dependencyData.NamedCache, dependencyData.RegionName, null);
                        }
                    }
                    finally
                    {
                        DependencyTrackerLocker.ExitWriteLock();
                    }

                    var cacheProviderErrorEventArgs = new CacheProviderErrorEventArgs()
                    {
                        Sender = this,
                        CacheKey = dependencyData == null ? "N/A" : dependencyData.Key,
                        ErrorMessage = "Fatal Exception for Query Notification subscription. Check the SQL Queries!",
                        State = dependencyData
                    };
                    if (this._cacheProvider != null)
                        this._cacheProvider.EventAggregatorInstance.GetEvent<CacheProviderErrorEvent>().Publish(cacheProviderErrorEventArgs);

                    break;
                default:
                    if (sender is SqlDependency)
                    {
                        CacheDependencyTracker cacheDependencyData;
                        var isDelayProcessing = false;
                        try
                        {
                            DependencyTrackerLocker.EnterReadLock();

                            cacheDependencyData = (from dependencyInfo in this._cacheDependencyTracker
                                                   let sqlInstanceIds = dependencyInfo.SqlDependencyInstances.Select(x => x.Id)
                                                   where sqlInstanceIds.Contains((sender as SqlDependency).Id)
                                                   select dependencyInfo).FirstOrDefault();

                            if (cacheDependencyData != null)
                            {
                                var message = string.Format("Notification received By [{0}] for Cache with key [{1}] in regionName [{2}] under NamedCache [{3}]",
                                                            ConfigurationManager.AppSettings["ApplicationName"]
                                                            , cacheDependencyData.Key
                                                            , cacheDependencyData.RegionName
                                                            , cacheDependencyData.NamedCache);
                                if (this._logWriter != null)
                                    this._logWriter.Write(message, this._cacheProvider.LoggingCategory, 1, (int)LoggingEvent.CacheNotification, TraceEventType.Critical, cacheDependencyData.Key);

                                sqlNotificationInfo = new SqlNotificationState
                                {
                                    DependencyInstance = sender as SqlDependency,
                                    NotificationEventArgs = e
                                };

                                if (cacheDependencyData.DelayInDependencyNotificationProcessing != TimeSpan.Zero)
                                {
                                    var timer = new Timer(DependencyNotificationProcessingCallback, sqlNotificationInfo,
                                                          cacheDependencyData.DelayInDependencyNotificationProcessing,
                                                          TimeSpan.Zero);

                                    sqlNotificationInfo.Sender = timer;
                                    isDelayProcessing = true;
                                }
                            }
                        }

                        finally
                        {
                            DependencyTrackerLocker.ExitReadLock();
                        }


                        if (!isDelayProcessing && sqlNotificationInfo != null)
                            this.DependencyNotificationProcessingCallback(sqlNotificationInfo);
                    }
                    break;
            }

            //}
            //finally
            //{
            //    if (keyBeingProcessed != string.Empty)
            //        lock (NotificationKeyListSerializer)
            //        {
            //            ManualResetEvent manualResetEvent;
            //            if (this._keysUnderDataChangeNotificationProcessing.TryGetValue(keyBeingProcessed, out manualResetEvent))
            //                manualResetEvent.Set();
            //        }
            //}
        }

        private void DependencyNotificationProcessingCallback(object state)
        {
            var notificationInfo = state as SqlNotificationState;

            if (notificationInfo == null)
                return;

            //Dispose the timer
            if (notificationInfo.Sender != null)
                notificationInfo.Sender.Dispose();

            var e = notificationInfo.NotificationEventArgs;

            CacheDependencyTracker cacheDependencyData;

            try
            {
                DependencyTrackerLocker.EnterWriteLock();

                cacheDependencyData = (from dependencyInfo in this._cacheDependencyTracker
                                       let sqlDependencyInstanceIds =
                                           dependencyInfo.SqlDependencyInstances.Select(x => x.Id)
                                       where
                                           sqlDependencyInstanceIds.Contains(
                                           notificationInfo.DependencyInstance.Id)
                                       select dependencyInfo).FirstOrDefault();

                if (cacheDependencyData != null)
                {
                    var message = string.Format("Processing notification for cache with key [{0}] in region [{1}] under NamedCache [{2}]",
                                                cacheDependencyData.Key, cacheDependencyData.RegionName, cacheDependencyData.NamedCache);

                    if (this._logWriter != null)
                        this._logWriter.Write(message, this._cacheProvider.LoggingCategory, 1, (int)LoggingEvent.CacheNotification, TraceEventType.Information, cacheDependencyData.Key);

                    bool isCacheEntryVersionStillSame;

                    if (this._cacheProvider.IsCacheItemVersioningSupported)
                    {
                        //If the version of the entry in cache and the one in the tracker are not same,simply return.
                        //Because, some other client has already processed in between.
                        isCacheEntryVersionStillSame =
                            this._cacheProvider.IsCacheVersionSame(cacheDependencyData.CacheItemDataVersion,
                                                                   cacheDependencyData.Key,
                                                                   cacheDependencyData.RegionName);
                    }
                    else
                        isCacheEntryVersionStillSame = true;

                    if (isCacheEntryVersionStillSame)
                    {
                        if (this._logWriter != null)
                            this._logWriter.Write("Cache Versions are still same. Hence, about to process it", this._cacheProvider.LoggingCategory, 1, (int)LoggingEvent.CacheNotification, TraceEventType.Information, cacheDependencyData.Key);

                        //Invalidate the Cache
                        var currentCacheName = string.Empty;

                        //Set Named cache if required.
                        if (this._cacheProvider.IsNamedCacheSupported)
                            currentCacheName = this._cacheProvider.CacheName;

                        if (this._cacheProvider.IsNamedCacheSupported
                            && !string.IsNullOrEmpty(cacheDependencyData.NamedCache)
                            && !currentCacheName.Equals(cacheDependencyData.NamedCache))
                        {
                            this._cacheProvider.SetNamedCache(cacheDependencyData.NamedCache);
                        }

                        //Remove Cache Entry
                        if (this._cacheProvider.IsRegionSupported
                            && !string.IsNullOrEmpty(cacheDependencyData.RegionName))
                        {
                            this._cacheProvider.Remove(cacheDependencyData.Key,
                                                       cacheDependencyData.RegionName);
                        }
                        else
                        {
                            this._cacheProvider.Remove(cacheDependencyData.Key);
                        }

                        //Restore the Named Cache (if there was one)
                        if (this._cacheProvider.IsNamedCacheSupported
                            && !string.IsNullOrEmpty(currentCacheName)
                            && !currentCacheName.Equals(cacheDependencyData.NamedCache))
                        {
                            this._cacheProvider.SetNamedCache(currentCacheName);
                        }
                    }


                    //unsubscribe the dependency notification.
                    if (cacheDependencyData.DependencyData.CacheDependencyType ==
                        CacheDependencyTypes.SQLDependency)
                    {
                        foreach (var sqlDependency in cacheDependencyData.SqlDependencyInstances)
                        {
                            sqlDependency.OnChange -= SqlDependency_OnChange;
                        }
                    }

                    this._cacheDependencyTracker.Remove(cacheDependencyData);

                    //If dependencies are to be cached, schedule caching the same on a separate threadpool thread.
                    if (cacheDependencyData.AreDependenciesCached)
                        ThreadPool.QueueUserWorkItem(EncacheDependencies);
                }
            }
            finally
            {
                DependencyTrackerLocker.ExitWriteLock();
            }

            if (cacheDependencyData != null && cacheDependencyData.CallbackManager != null)
                cacheDependencyData.CallbackManager.InvokeCallbacks();
        }

        /// <summary>
        /// This method stores the dependency list in the cache.  This list, in turn, is consumed
        /// by a windows service which manages the dependencies.
        /// </summary>
        /// <param name="state"></param>
        private void EncacheDependencies(object state)
        {
            //const string dependencyTrackerCacheKey = "AppFabricCacheProvider_Admin_DependencyTracker";
            var dependencyTrackerCacheKey = this._cacheProvider.CacheClientNodeIdentifier;

            try
            {
                DependencyTrackerLocker.EnterReadLock();

                //TODO: Store it in a separate Named Cache.
                if (this._cacheDependencyTracker.Count == 0)
                    this._cacheProvider.Remove(dependencyTrackerCacheKey);
                else
                    this._cacheProvider.Put(dependencyTrackerCacheKey, this._cacheDependencyTracker);
            }
            finally
            {
                DependencyTrackerLocker.ExitReadLock();
            }
        }

        private static String GetSQLQuery(SqlConnection connection, string table)
        {
            string[] restrictions;
            if (table.Contains("."))
            {
                var tableNameParts = table.Split(new[] { '.' });

                //Restrictions are mentioned in the following order:
                //TABLE_CATALOG,TABLE_SCHEMA,TABLE_NAME,COLUMN_NAME
                restrictions = new string[] { null, tableNameParts[0], tableNameParts[1], null };
            }
            else
            {
                restrictions = new string[] { null, null, table, null };
            }

            if (connection.State != ConnectionState.Open)
                connection.Open();

            var metadataTable = connection.GetSchema("Columns", restrictions);
            var columnNames = from row in metadataTable.AsEnumerable()
                              select row.Field<string>("COLUMN_NAME");

            var columnsForQuery = string.Join(",", columnNames.ToArray());

            var sqlQueryBuilder = new StringBuilder();
            sqlQueryBuilder.Append("SELECT ");
            sqlQueryBuilder.Append(columnsForQuery);
            sqlQueryBuilder.Append(" FROM ");
            sqlQueryBuilder.Append(table);
            return sqlQueryBuilder.ToString();
        }

        #endregion

        //TODO: Add functionality to StopMonitoring if there are no more SqlCommands with that connection.  
    }
}